using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using CodePlex.DnD4eCampaignManager.DnD4e;
using CodePlex.DnD4eCampaignManager.DnD4e.Enums;
using CodePlex.DnD4eCampaignManager.Services.Interfaces;

namespace CodePlex.DnD4eCampaignManager.Services
{
    public class DungeonsAndDragonsInsiderPlayerCharacterImporter : IDungeonsAndDragonsInsiderPlayerCharacterImporter
    {
        private PlayerCharacter _playerCharacter;
        private XmlElement _playerCharacterRoot;

        public bool CanFileBeImported(string filePath)
        {
            if ( IsPlayerCharacterFileValid(filePath) )
            {
                GetRawPlayerCharacterDataFromFile(filePath);
                XmlAttribute xmlAttribute = _playerCharacterRoot.Attributes["Version"];
                if ( xmlAttribute != null )
                {
                    return xmlAttribute.Value == "0.07a";
                }
            }
            return false;
        }

        public PlayerCharacter ImportPlayerCharacter(string filePath)
        {
            if ( CanFileBeImported(filePath) )
            {
                InitializePlayerCharacter();
                GetRawPlayerCharacterDataFromFile(filePath);
                LoadPlayerCharacterDataFromFile();
                return _playerCharacter;
            }
            return null;
        }

        private Alignment GetAlignmentFromRulesElement()
        {
            try
            {
                string alignment = GetRulesElementNameByTypeAsString("Alignment");
                return (Alignment) Enum.Parse(typeof ( Alignment ), alignment.Replace(" ", string.Empty));
            }
            catch
            {
                return Alignment.Unaligned;
            }
        }

        private string GetAllRulesElementNamesByTypeAsString(string ruleElementType)
        {
            XmlNodeList ruleElements = _playerCharacterRoot.SelectNodes(string.Format("//RulesElement[@type='{0}']", ruleElementType));
            if ( ThereAreNoSearchResults(ruleElements) )
            {
                return string.Empty;
            }
            List<string> elements = GetUniqueElementNamesFromNodeList(ruleElements);
            return GetSortedRuleNames(elements);
        }

        private int GetIntegerStatValueByName(string statisticName)
        {
            XmlNode statisticNode = _playerCharacterRoot.SelectSingleNode(string.Format("//Stat[@name='{0}']", statisticName));
            XmlAttribute xmlAttribute = statisticNode.Attributes["value"];
            if ( xmlAttribute != null )
            {
                int statValue;
                int.TryParse(xmlAttribute.Value, out statValue);
                return statValue;
            }
            return 0;
        }

        private string GetNodeTextAsString(string nodeName)
        {
            XmlNode statisticNode = _playerCharacterRoot.SelectSingleNode(string.Format("//{0}", nodeName));
            if ( statisticNode != null )
            {
                return statisticNode.InnerText.Trim();
            }
            return string.Empty;
        }

        private int GetNodeValueAsInt(string nodeName)
        {
            int intValueFromFile;
            string stringValueFromFile = GetNodeTextAsString(nodeName);
            int.TryParse(stringValueFromFile, out intValueFromFile);
            return intValueFromFile;
        }

        private string GetPowers(string type)
        {
            XmlNodeList ruleElements = _playerCharacterRoot.SelectNodes(string.Format("//Power[specific=' {0} ']", type));
            if ( ThereAreNoSearchResults(ruleElements) )
            {
                return string.Empty;
            }
            List<string> elements = GetUniqueElementNamesFromNodeList(ruleElements);
            elements.Remove("Melee Basic Attack");
            elements.Remove("Ranged Basic Attack");
            return GetSortedRuleNames(elements);
        }

        private void GetRawPlayerCharacterDataFromFile(string filePath)
        {
            XmlDocument xmlPlayerCharacterFileData = new XmlDocument();
            xmlPlayerCharacterFileData.Load(filePath);
            _playerCharacterRoot = xmlPlayerCharacterFileData.DocumentElement;
        }

        private string GetRulesElementNameByTypeAsString(string ruleElementType)
        {
            XmlNode ruleElement = _playerCharacterRoot.SelectSingleNode(string.Format("//RulesElement[@type='{0}']", ruleElementType));
            if ( ruleElement == null )
            {
                return string.Empty;
            }
            XmlAttribute attribute = ruleElement.Attributes["name"];
            return attribute == null ? string.Empty : attribute.Value.Trim();
        }

        private void InitializePlayerCharacter()
        {
            _playerCharacter = new PlayerCharacter(new Defenses(), new Powers(), new Senses(), new Abilities(), new HealingSurges(new HitPoints()),
                                                   new Initiative(), new Movement());
        }

        private void LoadPlayerCharacterAbilities()
        {
            _playerCharacter.AbilityStrength = GetIntegerStatValueByName("Strength");
            _playerCharacter.AbilityConstitution = GetIntegerStatValueByName("Constitution");
            _playerCharacter.AbilityDexterity = GetIntegerStatValueByName("Dexterity");
            _playerCharacter.AbilityIntelligence = GetIntegerStatValueByName("Intelligence");
            _playerCharacter.AbilityWisdom = GetIntegerStatValueByName("Wisdom");
            _playerCharacter.AbilityCharisma = GetIntegerStatValueByName("Charisma");
        }

        private void LoadPlayerCharacterBasics()
        {
            _playerCharacter.Name = GetNodeTextAsString("name");
            _playerCharacter.CharacterName = GetNodeTextAsString("name");
            _playerCharacter.CharacterHeight = GetNodeTextAsString("Height");
            _playerCharacter.CharacterWeight = GetNodeValueAsInt("Weight");
            _playerCharacter.CharacterAge = GetNodeValueAsInt("Age");
            _playerCharacter.CharacterAlignment = GetAlignmentFromRulesElement();
            _playerCharacter.CharacterExperiencePoints = GetNodeValueAsInt("Experience");
            _playerCharacter.CharacterClass = GetRulesElementNameByTypeAsString("Class");
            _playerCharacter.CharacterDeity = GetRulesElementNameByTypeAsString("Deity");
            _playerCharacter.CharacterParagonPath = GetRulesElementNameByTypeAsString("Paragon Path");
            _playerCharacter.CharacterEpicDestiny = GetRulesElementNameByTypeAsString("Epic Destiny");
            _playerCharacter.CharacterRace = GetRulesElementNameByTypeAsString("Race");
            _playerCharacter.CharacterFeats = GetAllRulesElementNamesByTypeAsString("Feat");
            _playerCharacter.CharacterRituals = GetAllRulesElementNamesByTypeAsString("Ritual");
        }

        private void LoadPlayerCharacterDataFromFile()
        {
            LoadPlayerCharacterBasics();
            LoadPlayerCharacterAbilities();
            LoadPlayerCharacterDefenses();
            LoadPlayerCharacterHealth();
            LoadPlayerCharacterMovement();
            LoadPlayerCharacterSenses();
            LoadPlayerCharacterPowersAndTactics();
        }

        private void LoadPlayerCharacterDefenses()
        {
            _playerCharacter.DefenseArmorClass = GetIntegerStatValueByName("AC");
            _playerCharacter.DefenseFortitude = GetIntegerStatValueByName("Fortitude Defense");
            _playerCharacter.DefenseReflex = GetIntegerStatValueByName("Reflex Defense");
            _playerCharacter.DefenseWillpower = GetIntegerStatValueByName("Will Defense");
        }

        private void LoadPlayerCharacterHealth()
        {
            _playerCharacter.HealingSurgesCurrent = GetIntegerStatValueByName("Healing Surges");
            _playerCharacter.HealingSurgesMaximum = GetIntegerStatValueByName("Healing Surges");
            _playerCharacter.HitPointsMaximum = GetIntegerStatValueByName("Hit Points");
            _playerCharacter.HitPointsCurrent = GetIntegerStatValueByName("Hit Points");
            _playerCharacter.HitPointsTemporary = 0;
            _playerCharacter.HealingSurgeValue = _playerCharacter.HitPointsMaximum / 4;
        }

        private void LoadPlayerCharacterMovement()
        {
            _playerCharacter.InitiativeRoll = 10;
            _playerCharacter.InitiativeModifier = GetIntegerStatValueByName("Initiative");
            _playerCharacter.MovementSpeed = GetIntegerStatValueByName("Speed");
            _playerCharacter.MovementSpeedOther = string.Empty;
            _playerCharacter.MovementStealth = GetIntegerStatValueByName("Stealth");
        }

        private void LoadPlayerCharacterPowersAndTactics()
        {
            _playerCharacter.PowersAtWill = GetPowers("At-Will");
            _playerCharacter.PowersEncounter = GetPowers("Encounter");
            _playerCharacter.PowersDaily = GetPowers("Daily");
            _playerCharacter.Tactics = string.Empty;
        }

        private void LoadPlayerCharacterSenses()
        {
            _playerCharacter.SensesPerception = GetIntegerStatValueByName("Perception");
            _playerCharacter.SensesPerceptionPassive = GetIntegerStatValueByName("Passive Perception");
            _playerCharacter.SensesInsight = GetIntegerStatValueByName("Insight");
            _playerCharacter.SensesInsightPassive = GetIntegerStatValueByName("Passive Insight");
            _playerCharacter.SensesOther = string.Empty;
        }

        private static string GetSortedRuleNames(IEnumerable<string> elements)
        {
            StringBuilder elementNames = new StringBuilder();
            foreach ( string element in elements.OrderBy(x => x) )
            {
                elementNames.AppendFormat(", {0}", element);
            }
            if ( elementNames.Length > 1 )
            {
                elementNames.Remove(0, 2);
            }
            return elementNames.ToString();
        }

        private static List<string> GetUniqueElementNamesFromNodeList(XmlNodeList ruleElements)
        {
            List<string> elements = new List<string>();
            foreach ( XmlNode element in ruleElements )
            {
                XmlAttribute attribute = element.Attributes["name"];
                string elementName = attribute == null ? string.Empty : attribute.Value.Trim();
                if ( !elements.Contains(elementName) )
                {
                    elements.Add(elementName);
                }
            }
            return elements;
        }

        private static bool IsPlayerCharacterFileValid(string filePath)
        {
            return File.Exists(filePath);
        }

        private static bool ThereAreNoSearchResults(XmlNodeList ruleElements)
        {
            return ruleElements == null || ruleElements.Count == 0;
        }
    }
}